package org.aplikator.client.shared.rpc.marshaller;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.aplikator.client.shared.descriptor.BinaryFieldDTO;
import org.aplikator.client.shared.descriptor.CheckBoxDTO;
import org.aplikator.client.shared.descriptor.ComboBoxDTO;
import org.aplikator.client.shared.descriptor.DateFieldDTO;
import org.aplikator.client.shared.descriptor.FunctionDTO;
import org.aplikator.client.shared.descriptor.FunctionWidgetDTO;
import org.aplikator.client.shared.descriptor.LabelFieldDTO;
import org.aplikator.client.shared.descriptor.PageHeaderDTO;
import org.aplikator.client.shared.descriptor.PanelDTO;
import org.aplikator.client.shared.descriptor.PropertyDTO;
import org.aplikator.client.shared.descriptor.RadioButtonDTO;
import org.aplikator.client.shared.descriptor.ReferenceFieldDTO;
import org.aplikator.client.shared.descriptor.RepeatedFormDTO;
import org.aplikator.client.shared.descriptor.SimpleBadgeDTO;
import org.aplikator.client.shared.descriptor.SimpleLabelDTO;
import org.aplikator.client.shared.descriptor.TableDTO;
import org.aplikator.client.shared.descriptor.TextAreaDTO;
import org.aplikator.client.shared.descriptor.TextFieldDTO;
import org.aplikator.client.shared.descriptor.ViewDTO;
import org.aplikator.client.shared.descriptor.WidgetDTO;
import org.aplikator.client.shared.descriptor.WidgetDTOFactory.FunctionWidgetDTOCommand;
import org.aplikator.client.shared.descriptor.WidgetDTOFactory.TableDTOCommand;
import org.aplikator.client.shared.descriptor.WidgetDTOFactory.TextFieldDTOCommand;
import org.aplikator.client.shared.descriptor.WidgetPropertyDTOBase;
import org.aplikator.client.shared.rpc.marshaller.DataMarshallingUtils.DataType;
import org.aplikator.server.descriptor.WidgetPropertyDescriptorBase;
import org.jboss.errai.marshalling.client.api.Marshaller;
import org.jboss.errai.marshalling.client.api.MarshallingSession;
import org.jboss.errai.marshalling.client.api.json.EJArray;
import org.jboss.errai.marshalling.client.api.json.EJObject;
import org.jboss.errai.marshalling.client.api.json.EJString;
import org.jboss.errai.marshalling.client.api.json.EJValue;
import org.jboss.errai.marshalling.client.util.MarshallUtil;

public class WidgetMarshallingUtils {

	
	private static final String TYPE = "type";

	
	public static enum WidgetType {

		panel {
			@Override
			public StringBuilder marshall(StringBuilder input, WidgetDTO val, MarshallingSession ctx) {
				PanelDTO p = (PanelDTO) val;
				input.append('{');
				defaultProps(input, val, this);
				input.append(',');
				MarshallingUtils.bool("frame", p.isFrame(), input).append(',');
				MarshallingUtils.bool("horizontal", p.isHorizontal(), input).append(',');
				children(input, (PanelDTO)val,ctx);
				input.append('}');
				return input;
			}

			
			@Override
			public WidgetDTO demarshall(EJValue ejval, MarshallingSession ctx) {
				if (ejval.isObject() != null) {
					PanelDTO panel = new PanelDTO();
					EJObject ejObject = ejval.isObject();
					defaultProps(ejObject, panel);
					children(ejObject, panel,ctx);
					if (ejObject.containsKey("frame")) {
						panel.setFrame(ejObject.get("frame").isBoolean().booleanValue());
					}
					if (ejObject.containsKey("horizontal")) {
						panel.setHorizontal(ejObject.get("horizontal").isBoolean().booleanValue());
					}
					return panel;
				} else return null;
			}


			@Override
			public Class handleType() {
				return PanelDTO.class;
			}


			@Override
			public String typeName() {
				return "panel";
			}
		},
		
		combo {
			@Override
			public StringBuilder marshall(StringBuilder input, WidgetDTO val, MarshallingSession ctx) {
				input.append('{');
				defaultProps(input, val, this);
				input.append(',');
				property(input, (ComboBoxDTO)val, ctx);
				input.append('}');
				return input;
			}

			
			@Override
			public WidgetDTO demarshall(EJValue ejval, MarshallingSession ctx) {
				if (ejval.isObject() != null) {
					ComboBoxDTO cb = new ComboBoxDTO();
					defaultProps(ejval.isObject(), cb);
					property(cb, ejval.isObject(), ctx);
					return cb;
				} else return null;
			}


			@Override
			public Class handleType() {
				return ComboBoxDTO.class;
			}


			@Override
			public String typeName() {
				return "combo";
			}
			
		}, 
		
		
		
		simplelabel {
			@Override
			public StringBuilder marshall(StringBuilder input, WidgetDTO val, MarshallingSession ctx) {
				input.append('{');
				defaultProps(input, val, this);
				input.append(',');
				property(input, (SimpleLabelDTO)val, ctx);
				input.append(',');
				MarshallingUtils.string("labelType", ((SimpleLabelDTO)val).getLabelType(), input);
				input.append('}');
				return input;
			}

			
			@Override
			public WidgetDTO demarshall(EJValue ejval, MarshallingSession ctx) {
				if (ejval.isObject() != null) {
					SimpleLabelDTO lb = new SimpleLabelDTO();
					defaultProps(ejval.isObject(), lb);
					property(lb, ejval.isObject(), ctx);
					if (DemarshallingUtils.containsAndNotNull(ejval.isObject(), "labelType")) {
						lb.setLabelType(ejval.isObject().get("labelType").isString().stringValue());
					}
					return lb;
				} else return null;
			}


			@Override
			public Class handleType() {
				return SimpleLabelDTO.class;
			}

			@Override
			public String typeName() {
				return "simplelabel";
			}
			
		},

		simplebadge {
			@Override
			public StringBuilder marshall(StringBuilder input, WidgetDTO val, MarshallingSession ctx) {
				input.append('{');
				defaultProps(input, val, this);
				input.append(',');
				property(input, (SimpleBadgeDTO)val, ctx);
				input.append(',');
				MarshallingUtils.string("badgeType", ((SimpleBadgeDTO)val).getBadgeType(), input);
				input.append('}');
				return input;
			}

			
			@Override
			public WidgetDTO demarshall(EJValue ejval, MarshallingSession ctx) {
				if (ejval.isObject() != null) {
					SimpleBadgeDTO lb = new SimpleBadgeDTO();
					defaultProps(ejval.isObject(), lb);
					property(lb, ejval.isObject(), ctx);
					if (DemarshallingUtils.containsAndNotNull(ejval.isObject(), "badgeType")) {
						lb.setBadgeType(ejval.isObject().get("badgeType").isString().stringValue());
					}
					return lb;
				} else return null;
			}


			@Override
			public Class handleType() {
				return SimpleBadgeDTO.class;
			}

			@Override
			public String typeName() {
				return "simplebadge";
			}
			
		},

		label {

			@Override
			public StringBuilder marshall(StringBuilder input, WidgetDTO val, MarshallingSession ctx) {
				input.append('{');
				defaultProps(input, val, this);
				input.append(',');
				property(input, (LabelFieldDTO)val, ctx);
				input.append('}');
				return input;
			}

			
			@Override
			public WidgetDTO demarshall(EJValue ejval, MarshallingSession ctx) {
				if (ejval.isObject() != null) {
					LabelFieldDTO lb = new LabelFieldDTO();
					defaultProps(ejval.isObject(), lb);
					property(lb, ejval.isObject(), ctx);
					return lb;
				} else return null;
			}


			@Override
			public Class handleType() {
				return LabelFieldDTO.class;
			}

			@Override
			public String typeName() {
				return "label";
			}
		}, 
		refefence {

			@Override
			public StringBuilder marshall(StringBuilder input, WidgetDTO val, MarshallingSession ctx) {
				input.append('{');
				ReferenceFieldDTO ref = (ReferenceFieldDTO)val;
				defaultProps(input, val, this);
				input.append(',');
				MarshallingUtils.key("child", input).append(':');
				lookup(((ReferenceFieldDTO)val).getChild()).marshall(input, ref.getChild(), ctx).append(',');
				MarshallingUtils.key("view", input).append(':');
				Marshaller<Object> marsh = ctx.getMarshallerInstance(ViewDTO.class.getName());
				input.append(marsh.marshall(ref.getView(), ctx));
				input.append(',');
				property(input, (ReferenceFieldDTO)val, ctx);
				input.append('}');
				return input;
			}

			@Override
			public WidgetDTO demarshall(EJValue ejval, MarshallingSession ctx) {
				if (ejval.isObject() != null) {
					EJObject ejObject = ejval.isObject();
					ReferenceFieldDTO ref = new ReferenceFieldDTO();
					defaultProps(ejval.isObject(), ref);
					if (ejObject.containsKey("child")) {
						WidgetDTO child = lookup(ejObject.get("child")).demarshall(ejObject.get("child"), ctx);
						ref.setChild(child);
					}
					if (ejObject.containsKey("view")) {
						Marshaller<Object> marsh = ctx.getMarshallerInstance(ViewDTO.class.getName());
						Object view = marsh.demarshall(ejObject.get("view"), ctx);
						ref.setView((ViewDTO) view);
					}
					property(ref, ejval.isObject(), ctx);
					return ref;
				} else return null;
			}

			@Override
			public Class handleType() {
				return ReferenceFieldDTO.class;
			}

			@Override
			public String typeName() {
				return "reference";
			}
			
		},
		
		checkbox {

			@Override
			public StringBuilder marshall(StringBuilder input, WidgetDTO val, MarshallingSession ctx) {
				input.append('{');
				defaultProps(input, val, this);
				input.append(',');
				property(input, (CheckBoxDTO)val, ctx);
				input.append('}');
				return input;
			}

			@Override
			public WidgetDTO demarshall(EJValue ejval, MarshallingSession ctx) {
				if (ejval.isObject() != null) {
					CheckBoxDTO bf = new CheckBoxDTO();
					defaultProps(ejval.isObject(), bf);
					property(bf, ejval.isObject(), ctx);
					return bf;
				} else return null;
			}

			@Override
			public Class handleType() {
				return CheckBoxDTO.class;
			}

			@Override
			public String typeName() {
				return "checkbox";
			}
			
		},
		radiobutton {
			@Override
			public StringBuilder marshall(StringBuilder input, WidgetDTO val, MarshallingSession ctx) {
				input.append('{');
				defaultProps(input, val, this);
				input.append(',');
				property(input, (RadioButtonDTO)val, ctx);
				input.append('}');
				return input;
			}

			@Override
			public WidgetDTO demarshall(EJValue ejval, MarshallingSession ctx) {
				if (ejval.isObject() != null) {
					RadioButtonDTO bf = new RadioButtonDTO();
					defaultProps(ejval.isObject(), bf);
					property(bf, ejval.isObject(), ctx);
					return bf;
				} else return null;
			}

			@Override
			public Class handleType() {
				return RadioButtonDTO.class;
			}

			@Override
			public String typeName() {
				return "radio";
			}
			
		},
		
		pageheader {

			@Override
			public StringBuilder marshall(StringBuilder input, WidgetDTO val,
					MarshallingSession ctx) {
				input.append('{');
				defaultProps(input, val, this);
				input.append(',');
				property(input, (PageHeaderDTO)val, ctx);
				input.append('}');
				return input;
			}

			@Override
			public WidgetDTO demarshall(EJValue ejval, MarshallingSession ctx) {
				if (ejval.isObject() != null) {
					PageHeaderDTO ph = new PageHeaderDTO();
					defaultProps(ejval.isObject(), ph);
					property(ph, ejval.isObject(), ctx);
					return ph;
				} else return null;
			}

			@Override
			public Class handleType() {
				return PageHeaderDTO.class;
			}

			@Override
			public String typeName() {
				return "pageheader";
			}
			
		},
		
		binary {

			@Override
			public StringBuilder marshall(StringBuilder input, WidgetDTO val, MarshallingSession ctx) {
				BinaryFieldDTO bf = (BinaryFieldDTO) val;
				input.append('{');
				defaultProps(input, val, this);
				input.append(',');
				MarshallingUtils.string("entityId", bf.getEntityId(), input);
				input.append(',');
				property(input, (BinaryFieldDTO)val, ctx);
				input.append('}');
				return input;
			}

			@Override
			public WidgetDTO demarshall(EJValue ejval, MarshallingSession ctx) {
				if (ejval.isObject() != null) {
					BinaryFieldDTO bf = new BinaryFieldDTO();
					defaultProps(ejval.isObject(), bf);
					bf.setEntityId(ejval.isObject().get("entityId").isString().stringValue());
					property(bf, ejval.isObject(), ctx);
					return bf;
				} else return null;
			}

			@Override
			public Class handleType() {
				return BinaryFieldDTO.class;
			}

			@Override
			public String typeName() {
				return "binary";
			}
			
		}, 
		date {

			@Override
			public StringBuilder marshall(StringBuilder input, WidgetDTO val, MarshallingSession ctx) {
				DateFieldDTO df = (DateFieldDTO) val;
				input.append('{');
				defaultProps(input, val, this);
				input.append(',');
				property(input, (DateFieldDTO)val, ctx);
				input.append('}');
				return input;
			}

			@Override
			public WidgetDTO demarshall(EJValue ejval, MarshallingSession ctx) {
				if (ejval.isObject() != null) {
					DateFieldDTO df = new DateFieldDTO();
					defaultProps(ejval.isObject(), df);
					property(df, ejval.isObject(), ctx);
					return df;
				} else return null;
			}


			@Override
			public Class handleType() {
				return DateFieldDTO.class;
			}

			@Override
			public String typeName() {
				return "date";
			}
		},
		repeatedform {

			@Override
			public StringBuilder marshall(StringBuilder input, WidgetDTO val, MarshallingSession ctx) {
				RepeatedFormDTO repeated = (RepeatedFormDTO) val;
				input.append('{');
				defaultProps(input, val, this);
				input.append(',');
				MarshallingUtils.key("view", input).append(':');
				Marshaller<Object> marsh = ctx.getMarshallerInstance(ViewDTO.class.getName());
				input.append(marsh.marshall(repeated.getView(), ctx));
				input.append(',');
				property(input, (RepeatedFormDTO)val, ctx);
				input.append('}');
				return input;
			}

			@Override
			public WidgetDTO demarshall(EJValue ejval, MarshallingSession ctx) {
				if (ejval.isObject() != null) {
					EJObject ejObject = ejval.isObject();
					
					RepeatedFormDTO repForm = new RepeatedFormDTO();
					defaultProps(ejval.isObject(), repForm);
					if (ejObject.containsKey("view")) {
						Marshaller<Object> marsh = ctx.getMarshallerInstance(ViewDTO.class.getName());
						Object view = marsh.demarshall(ejObject.get("view"), ctx);
						repForm.setView((ViewDTO) view);
					}
					property(repForm, ejval.isObject(), ctx);

					return repForm;
				} else return null;
			}


			@Override
			public Class handleType() {
				return RepeatedFormDTO.class;
			}

			@Override
			public String typeName() {
				return "repeated";
			}
		},
		table {

			@Override
			public StringBuilder marshall(StringBuilder input, WidgetDTO val,
					MarshallingSession ctx) {
				TableDTO tbdo = (TableDTO) val;
				input.append('{');
				defaultProps(input, val, this);
				input.append(',');
				MarshallingUtils.key("view", input).append(':');
				
				Marshaller<Object> marsh = ctx.getMarshallerInstance(ViewDTO.class.getName());
				input.append(marsh.marshall(tbdo.getView(), ctx));

				input.append(',');
				property(input, (TableDTO)val, ctx);

				input.append('}');
				return input;
			}

			@Override
			public WidgetDTO demarshall(EJValue ejval, MarshallingSession ctx) {
				if (ejval.isObject() != null) {
					EJObject ejObject = ejval.isObject();
					TableDTO td = new TableDTO();
					defaultProps(ejval.isObject(), td);
					if (ejObject.containsKey("view")) {
						Marshaller<Object> marsh = ctx.getMarshallerInstance(ViewDTO.class.getName());
						ViewDTO vdto = (ViewDTO) marsh.demarshall(ejObject.get("view"), ctx);
						td.setView(vdto);
					}
					property(td, ejval.isObject(), ctx);
					return td;
				} else return null;
			}

			@Override
			public Class handleType() {
				return TableDTO.class;
			}

			@Override
			public String typeName() {
				return "table";
			}
			
		},
		
		textarea {

			@Override
			public StringBuilder marshall(StringBuilder input, WidgetDTO val, MarshallingSession ctx) {
				TextAreaDTO table = (TextAreaDTO) val;
				input.append('{');
				defaultProps(input, val, this);
				input.append(',');
				MarshallingUtils.number("rows", table.getRows(), input);
				input.append(',');
				property(input, (TextAreaDTO)val, ctx);
				input.append('}');
				return input;
			}

			@Override
			public WidgetDTO demarshall(EJValue ejval, MarshallingSession ctx) {
				if (ejval.isObject() != null) {
					EJObject ejObject = ejval.isObject();
					
					TextAreaDTO tDTO = new TextAreaDTO();
					defaultProps(ejval.isObject(), tDTO);
					if (ejObject.containsKey("rows")) {
						tDTO.setRows(ejObject.get("rows").isNumber().intValue());
					}
					property(tDTO, ejval.isObject(), ctx);
					
					return tDTO;
				} else return null;
			}


			@Override
			public Class handleType() {
				return TextAreaDTO.class;
			}

			@Override
			public String typeName() {
				return "textarea";
			}
		},
		functionfield {
			@Override
			public StringBuilder marshall(StringBuilder input, WidgetDTO val, MarshallingSession ctx) {
				FunctionWidgetDTO fw = (FunctionWidgetDTO) val;
				input.append('{');
				defaultProps(input, val, this);
				input.append(',');
				MarshallingUtils.key("function", input).append(':');
				Marshaller<Object> marshaller = ctx.getMarshallerInstance(FunctionDTO.class.getName());
				input.append(marshaller.marshall(fw.getFunction(), ctx));
				input.append('}');
				return input;
			}

			@Override
			public WidgetDTO demarshall(EJValue ejval, MarshallingSession ctx) {
				if (ejval.isObject() != null) {
					EJObject ejObject = ejval.isObject();
					if (DemarshallingUtils.containsAndNotNull(ejObject, "function")) {
						Marshaller<Object> marshaller = ctx.getMarshallerInstance(FunctionDTO.class.getName());
						Object fdto = marshaller.demarshall(ejObject.get("function"), ctx);
						FunctionWidgetDTO tDTO = new FunctionWidgetDTO((FunctionDTO) fdto);
						defaultProps(ejval.isObject(), tDTO);
						return tDTO;
					} else return null;
				} else return null;
			}


			@Override
			public Class handleType() {
				return FunctionWidgetDTO.class;
			}

			@Override
			public String typeName() {
				return "function";
			}
			
		},
		textfield {

			@Override
			public StringBuilder marshall(StringBuilder input, WidgetDTO val, MarshallingSession ctx) {
				input.append('{');
				defaultProps(input, val, this);
				input.append(',');
				property(input, (TextFieldDTO)val, ctx);
				input.append('}');
				return input;
			}

			@Override
			public WidgetDTO demarshall(EJValue ejval, MarshallingSession ctx) {
				if (ejval.isObject() != null) {
					TextFieldDTO tDTO = new TextFieldDTO();
					defaultProps(ejval.isObject(), tDTO);
					property(tDTO, ejval.isObject(), ctx);
					return tDTO;
				} else return null;
			}


			@Override
			public Class handleType() {
				return TextFieldDTO.class;
			}

			@Override
			public String typeName() {
				return "textfield";
			}
		}; 


		public abstract StringBuilder marshall(StringBuilder input, WidgetDTO val, MarshallingSession ctx);
	
		public abstract WidgetDTO demarshall(EJValue ejval, MarshallingSession ctx);
		
		public abstract Class handleType();
		
		public abstract String typeName();

		public static WidgetType lookup(WidgetDTO val) {
			WidgetType[] vals = values();
			for (WidgetType dt : vals) {
				if (dt.handleType().equals(val.getClass())) {
					return dt;
				}
			}
			return null;
		}
		
		public static WidgetType lookup(EJValue val) {
			if (val.isObject() != null) {
				EJObject obj = val.isObject();
				if (obj.containsKey(TYPE)) {
					EJString ejString = obj.get(TYPE).isString();
					if (ejString != null) {
						WidgetType[] vals = values();
						for (WidgetType wt : vals) {
							if (wt.typeName().equals(ejString.stringValue())) {
								return wt;
							}
						}
					}
				}
			} 
			return null;
		}

		
	}

	
	
	static void defaultProps(EJObject ejObj, WidgetDTO w) {
		if (ejObj.containsKey("size") && (ejObj.get("size").isNumber() != null)) {
			w.setSize(ejObj.get("size").isNumber().intValue());
		}
		if (ejObj.containsKey("formatPattern") && (ejObj.get("formatPattern").isString() != null)) {
			w.setFormatPattern(ejObj.get("formatPattern").isString().stringValue());
		} 
		if (ejObj.containsKey("enabled") && (ejObj.get("enabled").isBoolean() != null)) {
			w.setEnabled(ejObj.get("enabled").isBoolean().booleanValue());
		}			
	}
	

	static void defaultProps(StringBuilder builder, WidgetDTO w, WidgetType type) {
		MarshallingUtils.number("size", w.getSize(), builder).append(',');
		MarshallingUtils.string("formatPattern", w.getFormatPattern(), builder).append(',');
		MarshallingUtils.bool("enabled", w.isEnabled(), builder).append(',');
		MarshallingUtils.string("type", type.typeName(), builder);
	}

	static void property(StringBuilder builder,WidgetPropertyDTOBase p, MarshallingSession ctx) {
		MarshallingUtils.key("property", builder).append(':');
		Marshaller<Object> marshaller = ctx.getMarshallerInstance(PropertyDTO.class.getName());
		builder.append(marshaller.marshall(p.getProperty(), ctx));
	}
	static void property(WidgetPropertyDTOBase wbase,EJObject obj, MarshallingSession ctx) {
		if (obj.containsKey("property")) {
			Marshaller<Object> marshaller = ctx.getMarshallerInstance(PropertyDTO.class.getName());
			wbase.setProperty((PropertyDTO) marshaller.demarshall(obj.get("property"), ctx));
		}
	}
	
	static void children(StringBuilder builder, PanelDTO panel, MarshallingSession ctx) {
		MarshallingUtils.key("children", builder).append(':').append('[');
		List list = panel.getChildren();
		boolean first = true;
		for (Object o : list) {
			WidgetDTO w = (WidgetDTO) o;
			if (!first) builder.append(',');
			WidgetType wtype = WidgetType.lookup(w);
			if (wtype != null) {
				wtype.marshall(builder, w, ctx);
			} else throw new IllegalArgumentException("notsuported type '"+w.getClass().getName()+"'");
			first = false;
		}
		builder.append(']');
	}

	static void children(EJObject ejObj, PanelDTO panel, MarshallingSession ctx) {
		if (ejObj.containsKey("children")) {
			List<WidgetDTO> children = new ArrayList<WidgetDTO>();
			EJArray ejArray = ejObj.get("children").isArray();
			if (ejArray != null) {
				for (int i = 0, ll = ejArray.size(); i < ll; i++) {
					EJValue value = ejArray.get(i);
					WidgetType wType = WidgetType.lookup(value);
					if (wType != null) {
						WidgetDTO widget = wType.demarshall(value, ctx);
						children.add(widget);
					} else throw new UnsupportedOperationException("type not supported");
				} 
			}
			panel.setChildren(children);
		}
	}

}
